﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.ServiceModel;
using Sern.Abstracts.Tracker.DataContracts;
using Sern.Abstracts.Tracker.ServiceContracts;
using Sern.Common;
using Microsoft.Practices.Unity;
using Sern.ServiceImplementation.TrackerDefinition.Data;

namespace Sern.ServiceImplementation.TrackerDefinition.Service
{
    /// <summary>
    /// An implementation of tracker host
    /// </summary>
    public class TrackerHost : AbstractHostBase, ITrackerHost
    {        

        protected override ClientType HostType
        {
            get
            {
                return ClientType.TrackerHost;
            }
        }

        protected override void DisposeMember(Guid id)
        {
            EndAGivenJob(id);
        }


        public TrackerHost(string hostName) : base(hostName){}

        /// <summary>
        /// Creates a new job, associate it to a tracker and then host the listener.
        /// </summary>
        /// <param name="jobId1"> </param>
        /// <param name="keywords"> </param>
        /// <param name="description"> </param>
        /// <returns></returns>
        public void StartANewJob(Guid jobId, IEnumerable<string> keywords, string description)
        {
            Uri trackerBaseUri;
            BindingScheme trackerBindingScheme;
            Uri trackerRelativeUri;
            BuildTrackerUriAndBindingInformation(out trackerBaseUri, out trackerBindingScheme, out trackerRelativeUri);

            

            Uri trackerTelemetryBaseUri;
            Tracker telemetryReceiverSingleton;
            Uri trackerTelemetryRelativeUri;
            BindingScheme trackerTelemetryBindingScheme;
            var trackerTelemetryId = BuildTrackerTelemetryUriAndBindingInformation(trackerBindingScheme, out trackerTelemetryBaseUri, out trackerTelemetryRelativeUri, out trackerTelemetryBindingScheme);
           

            new Tracker().Subscribe(jobId, Guid.NewGuid(), ClientType.TrackerTelemetry, new Uri(trackerTelemetryBaseUri,trackerTelemetryRelativeUri).AbsoluteUri,null,null,AbsoluteUri);


            RegisterJobToPersistence(keywords, description, trackerRelativeUri, trackerBaseUri, jobId);
        }

        private void BuildTrackerUriAndBindingInformation(
                                                            out Uri trackerBaseUri, 
                                                            out BindingScheme trackerBindingScheme,
                                                            out Uri trackerRelativeUri)
        {
            trackerBindingScheme = ComponentLocator.Unity.Resolve<IBindingFactory>()[ClientType.Tracker]();

            trackerRelativeUri = new Uri(ClientType.Tracker.ToString(), UriKind.Relative);

            var trackerUriBuilder = new UriBuilder();
            trackerUriBuilder.Host = HostName;
            trackerUriBuilder.Scheme = trackerBindingScheme.Scheme;
            trackerUriBuilder.Port = trackerBindingScheme.Port ?? -1;
            trackerBaseUri = trackerUriBuilder.Uri;
        }

        private Guid BuildTrackerTelemetryUriAndBindingInformation(BindingScheme trackerBindingScheme,
                                                                   out Uri trackerTelemetryBaseUri,
                                                                   out Uri trackerTelemetryRelativeUri,
                                                                   out BindingScheme trackerTelemetryBindingScheme)
        {
            Guid trackerTelemetryId = Guid.NewGuid();
            trackerTelemetryBindingScheme = ComponentLocator.Unity.Resolve<IBindingFactory>()[ClientType.TrackerTelemetry]();
            trackerTelemetryRelativeUri = new Uri(ClientType.TrackerTelemetry + "/" + trackerTelemetryId.ToString("N"),
                                                  UriKind.Relative);

            var trackerTelemetryUriBuilder = new UriBuilder();
            trackerTelemetryUriBuilder.Host = HostName;
            trackerTelemetryUriBuilder.Scheme = trackerTelemetryBindingScheme.Scheme;
            trackerTelemetryUriBuilder.Port = trackerTelemetryBindingScheme.Port ?? -1;
            trackerTelemetryBaseUri = trackerTelemetryUriBuilder.Uri;
            return trackerTelemetryId;
        }

        
        private static void RegisterJobToPersistence(IEnumerable<string> keywords, string description, Uri trackerRelativeUri,
                                                     Uri trackerBaseUri, Guid jobId)
        {


            var jobRepository = ComponentLocator.Unity.Resolve<IRepository<Job>>();
            var keywordsRepository = ComponentLocator.Unity.Resolve<IRepository<JobKeyword>>();

            //If not already exists, then create, otherwise, it may just be a fail over tracker cluster. TODO: Eventually these failver should be registered to the DB, and copied as part of the distributed tracker efforts.
            if (!jobRepository.Records.Any(j => j.Id == jobId))
            {
                //Register the tracker so it will be discoverable.
                var newJobWithDescription = new JobDescription
                    {
                        Id = jobId,
                        TrackerUri = new Uri(trackerBaseUri, trackerRelativeUri).AbsoluteUri,
                        StartTme = DateTime.UtcNow,
                        EndTime = null,
                        Description = description
                    };

                jobRepository.Add(newJobWithDescription);
                keywordsRepository.AddMany(keywords.Select(kw => new JobKeyword
                    {
                        Id = Guid.NewGuid(),
                        Word = kw
                    }));

                keywordsRepository.Save();
            }
        }

        /// <summary>
        /// Close the listener and remove it from the collection.
        /// </summary>
        public void EndAGivenJob(Guid jobId)
        {
            

            var trackerInstance = new Tracker();
            var telemetryClientInfo = trackerInstance
                .FindActiveClientInfo(jobId, ClientType.TrackerTelemetry,
                    Guid.Empty, new Guid(),
                    null,
                    null
                );
                        
            
            //Mark these clients as done in the database.
            trackerInstance.Unsubscribe(jobId);
            trackerInstance.Unsubscribe(telemetryClientInfo.Id);
           
            
            //Mark the job as having ended.
            var jobRepository = ComponentLocator.Unity.Resolve<IRepository<Job>>();

            //NOTE: Ending a job that has been ended will result in an exception thrown!
            var endingJob = jobRepository.Records.Single(job => job.Id == jobId
                                                                && job.EndTime == null);
            endingJob.EndTime = DateTime.UtcNow;
            jobRepository.Save();            

        }

        /// <summary>
        /// List all the available jobs by the predicate.
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="skip"></param>
        /// <param name="take"></param>
        /// <returns></returns>
        public IEnumerable<TrackerInfo> List(Expression<Func<TrackerInfo, bool>> predicate, int skip = 0, int take = 25)
        {
            var jobDescriptions = ComponentLocator.Unity.Resolve<IRepository<Job>>().Records.OfType<JobDescription>();
            var jobKeywords = ComponentLocator.Unity.Resolve<IRepository<JobKeyword>>().Records;

            var _ = (from jobDescription in jobDescriptions
                     join jobKeyword in jobKeywords
                         on jobDescription.Id equals jobKeyword.JobId
                         into jobDescriptionAndKeywords
                     select new TrackerInfo
                                {
                                    Description = jobDescription.Description,
                                    Uri = jobDescription.TrackerUri,
                                    EndTime = jobDescription.EndTime,
                                    StartTime = jobDescription.StartTme,
                                    Keywords = jobDescriptionAndKeywords.Select(__ => __.Word).DefaultIfEmpty()
                                }
                    ).Where(predicate).Skip(skip).Take(take).ToArray();

            return _;

        }

        /// <summary>
        /// Updates the descriptions and keywords information associated to a tracker.
        /// </summary>
        /// <param name="jobId1"> </param>
        /// <param name="keywords"></param>
        /// <param name="description"></param>
        public void UpdateTrackerInformation(Guid jobId, IEnumerable<string> keywords, string description)
        {
            var jobDescriptions = ComponentLocator.Unity.Resolve<IRepository<Job>>().Records.OfType<JobDescription>();
            var jobKeywords = ComponentLocator.Unity.Resolve<IRepository<JobKeyword>>().Records;
            

            var jobDescription = jobDescriptions.Where(_ => _.Id == jobId).Single();
            jobDescription.Description = description;

            var oldJobKeywords = jobKeywords.Where(_ => _.JobId == jobId).ToArray();
            ComponentLocator.Unity.Resolve<IRepository<JobKeyword>>().DeleteMany(oldJobKeywords);
            ComponentLocator.Unity.Resolve<IRepository<JobKeyword>>().AddMany(keywords.Select(_ => new JobKeyword{Id = Guid.NewGuid(), JobId = jobId, Word = _}));

            ComponentLocator.Unity.Resolve<IRepository<JobKeyword>>().Save();
        }        
    }
}
